﻿using System;
using System.Text;
using System.Threading;
using System.Net.Sockets;
using System.Diagnostics;
using System.IO;
using generic = Bettzueche.RLGlue.Generics;
using Bettzueche.RLGlue.RLTypes;

namespace Bettzueche.RLGlue.Codec.Network {

    /// <summary>
    /// Data content of a DataReceived event.
    /// </summary>
    public class ReceiveEventArgs : EventArgs {
        /// <summary>
        /// Gets or sets the length.
        /// </summary>
        /// <value>
        /// The length.
        /// </value>
        public int Length { get; set; }
        /// <summary>
        /// Gets or sets the data.
        /// </summary>
        /// <value>
        /// The data.
        /// </value>
        public Byte[] Data { get; set; }
    }

    /// <summary>
    /// Netzwerkkommunikation mit der RLGlue.exe.<para>
    /// Bietet Möglichkeiten Daten für das Senden bereitzustellen und abzuschicken, bzw. zu empfangen aun auszulesen.</para>
    /// </summary>
    /// <remarks>
    /// This Code is based on the Java RL-Glue http://rl-glue-ext.googlecode.com/ ,Copyright (C) 2007, Brian Tanner.
    /// <para>
    /// This class does the heavy lifting of sendig and receiving data over the
    /// network. It is used also by both the Java and Matlab codecs.
    /// </para>
    /// </remarks>
    public class Network {


        #region Global Constants

        internal const String kDefaultHost = "127.0.0.1";
        internal const int kDefaultPort = 4096;
        internal const int kRetryTimeout = 2;
        /// <summary>Defaultgröße der Sende- und Empfangspuffer: 4 kB.</summary>
        protected const int kByteBufferDefaultSize = 4096;
        

        #endregion

        /// <summary>
        /// Event, dass den Datenempfang von RL-Glue signalisiert.<para>
        /// Das <see cref="ReceiveEventArgs"/> beinhaltet die gelesenen Daten.</para>
        /// </summary>
        public event EventHandler<ReceiveEventArgs> DataReceived; 
        protected TcpClient clientSocket;
        //private bool blocking = true;
        //private byte[] recvBuffer;
        private MemoryStream recvMemory;
        private MemoryStream sendMemory;
        private BinaryReader recvMemReader;
        private BinaryWriter sendMemWriter;
        //private int recvOffset = 0;
        //private byte[] sendBuffer;
        //private int sendOffset = 0;
        //private bool debug = false;



        /// <summary>
        /// Initializes a new instance of the <see cref="Network"/> class.
        /// </summary>
        internal Network() {
            //recvBuffer = new byte[kByteBufferDefaultSize];
            //sendBuffer = new byte[kByteBufferDefaultSize];

            recvMemory = new MemoryStream(kByteBufferDefaultSize); //(4KB)
            sendMemory = new MemoryStream(kByteBufferDefaultSize);
            recvMemReader = new RLGlueBinaryReader(recvMemory);
            sendMemWriter = new RLGlueBinaryWriter(sendMemory);
        }

        /// <summary>
        /// Verbindung zum RL-Glue unter angegebener Adresses aufbauen.<para>
        /// Modus: Nicht-Blockierendes schreiben.</para>
        /// </summary>
        /// <param name="host">RL-Glue Hostname</param>
        /// <param name="port">RL-Glue Port</param>
        /// <param name="retryTimeout">Zeit in Millis, nach der ein fehlgeschlagener Verbindungsversuch wiederholt werden soll.
        ///                             0 = kein erneuter Versuch, Exception schmeißen.</param>
        /// <exception cref="SocketException">An error occurred when accessing the socket</exception>
        /// <exception cref="ArgumentNullException">The hostname parameter is null.</exception>
        /// <exception cref="ArgumentOutOfRangeException">The port parameter is not between MinPort and MaxPort.</exception>
        internal void Connect(String host, int port, int retryTimeout) {
            Connect(host, port, retryTimeout, true);
        }
        

        /// <summary>
        /// Verbindung zum RL-Glue unter angegebener Adresses aufbauen.
        /// </summary>
        /// <param name="host">RL-Glue Hostname</param>
        /// <param name="port">RL-Glue Port</param>
        /// <param name="retryTimeout">Zeit in Millis, nach der ein fehlgeschlagener Verbindungsversuch wiederholt werden soll.
        ///                             0 = kein erneuter Versuch, Exception schmeißen.</param>
        /// <param name="blocking">blockierendes schreiben [ja/nein]</param>
        /// <exception cref="SocketException">An error occurred when accessing the socket</exception>
        /// <exception cref="ArgumentNullException">The hostname parameter is null.</exception>
        /// <exception cref="ArgumentOutOfRangeException">The port parameter is not between MinPort and MaxPort.</exception>
        internal void Connect(String host, int port, int retryTimeout, bool blocking) {
            try {	        
                clientSocket = new TcpClient(host, port); //beinhaltet connect!
                clientSocket.Client.Blocking = blocking;
            }
            catch (Exception ex) {
                Console.WriteLine("Es konnte keine Verbindung zu RL-Glue aufgebaut werden:\n" + ex.Message);
                Debug.Fail("Es konnte keine Verbindung zu RL-Glue aufgebaut werden",ex.Message);
                if (retryTimeout == 0)
                    throw ex;
                Thread.Sleep(retryTimeout);
                Connect(host, port, retryTimeout, blocking);
            }
        }


        /// <summary>
        /// This can be used in NON-BLOCKING mode to be sure that the connection was made.
        /// Probably call this in a loop.  In BLOCKING mode, you don't need to call this.
        /// </summary>
        /// <returns>Gibt an, ob eine TCP-Verbindung besteht.</returns>
        /// <remarks>
        /// This can be used in NON-BLOCKING mode to be sure that the connection was made.
        /// Probably call this in a loop.  In BLOCKING mode, you don't need to call this.
        /// @since 2.02
        /// @return
        /// @throws java.io.IOException
        /// </remarks>
        internal bool EnsureConnected() {
            Trace.Assert(clientSocket != null);
            if (clientSocket != null) {
                return clientSocket.Connected;
            }
            return false;
        }

        /// <summary>
        /// Schließt die Verbindung zu RL-Glue.exe und gibt assoziierte Ressourcen frei.
        /// </summary>
        internal void Close() {
            clientSocket.Close();
        }

        /// <summary>
        /// Sendet den Inhalt des internen Schreib-Puffers, ausgehend von der aktuellen Schreibposition.
        /// </summary>
        internal void Send() {
            NetworkStream serverStream = clientSocket.GetStream();
            int size = (int)(sendMemory.Length - sendMemory.Position);
            serverStream.Write(sendMemory.GetBuffer(),(int)sendMemory.Position,size);
            sendMemory.Seek(size,SeekOrigin.Current);
            serverStream.Flush();
            //serverStream.BeginWrite(buffer,offset,size,ar => serverStream.EndWrite, null);
        }

        /// <summary>
        /// Liest die angegebenes Anzahl Bytes vom RL-Glue.<para>
        /// Benutzen Sie alternativ (besser) <see cref="RecvAsync"/></para>
        /// </summary>
        /// <param name="size">Anzahhl zu lesender Bytes</param>
        /// <returns>Anzahl gelesener Bytes. 0, falls keine Verbindung besteht.</returns>
        /// <exception cref="RLGlueException">NetworkStream oder Socket ist geschlossen oder Error beim Zugriff auf Socket.</exception>
        /// <remarks>
        /// This method has been updated. It will work as it used to, before we added
        /// NON-BLOCKING mode.  It will either return after reading size (or more) bytes
        /// or it will throw an exception.
        /// @param size
        /// @return
        /// @throws java.io.IOException
        /// </remarks>
        internal int Recv(int size) {
            if(size==0) return 0;
            if (!EnsureConnected())
                return 0;
            //recvOffset = 0;
            int amountReceived = RecvNonBlock(size);
            while (amountReceived == 0) {
                Thread.Yield();
                amountReceived = RecvNonBlock(size);
            }
            return amountReceived;;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="size">Anzahl zu lesender Bytes.</param>
        /// <returns>Anzahl gelesener Bytes.</returns>
        /// <exception cref="RLGlueException">NetworkStream oder Socket ist geschlossen oder Error beim Zugriff auf Socket.</exception>
        /// <remarks>
        /// Java-Code Beschreibung:
        /// This method has been added.  If the socket is in non-blockig mode, the
        /// It will return 0 if there is no data available.
        /// 
        /// If you use the NON-BLOCKING option, it is possible that if there is no data available
        /// (for example, other components have not yet connected)
        /// then recv will return 0.  If recv gets ANYTHING when it first tries,
        /// then it WILL continue and poll until it gets all the data it was asked for.
        /// 
        /// It also will not throw a RLGlueDisconnectException if it socketChannel.read returns
        /// -1 before size bytes have been read.  This is indicative that the other end of the
        /// socket is closed (IE, RL-Glue was quit or crashed).
        /// @param size @return @throws java.io.IOException @since 2.02
        /// </remarks>
        internal int RecvNonBlock(int size) {
            int recvTotal = 0;
            this.ensureRecvCapacityRemains(size);
            try {
                NetworkStream serverStream = clientSocket.GetStream();
                if (!serverStream.CanRead) {
                    throw new RLGlueException("ERROR: .NET Codec wanted to read, but can't read from NetworkConnection.\n\tRL-Glue probably closed the connection.");
                }

                while (recvTotal < size) {
                    int recvSize = serverStream.Read(recvMemory.GetBuffer(), (int)(recvMemory.Position), size);
                    recvTotal += recvSize;
                    recvMemory.Seek(recvSize, SeekOrigin.Current);
                }
            }
            catch (Exception ex) {
                if (ex is RLGlueException)
                    throw ex;
                throw new RLGlueException("ERROR at reading from Network",ex);
            }
            //if (recvSize == -1) {
            //    close();
            //    throw new RLGlueDisconnectException("ERROR: Java Codec was expecting read: " + size + " bytes but only received: " + recvTotal + ".\n\tRL-Glue probably closed the connection.");
            //}
            if (recvTotal == 0) {
                //This MUST be the first iteration of the loop because we return, if we receive 0 bytes on the first read.
                return 0;
            }
            return recvTotal;
            //IAsyncResult ar = serverStream.BeginRead(recvBuffer,0,size,null,serverStream);
            //ar.AsyncWaitHandle.WaitOne(); //dieser Thread blockiert bis Read fertig ist.
            //return serverStream.EndRead(ar);
            
        }

        /// <summary>
        /// Asynchrones Lesen. Das Ende des Lesevorgangs wird durch das <see cref="DataReceived"/> Event signalisiert.
        /// </summary>
        /// <param name="size">Anzahl zu lesender Bytes.</param>
        internal void RecvAsync(int size) {
            NetworkStream serverStream = clientSocket.GetStream();
            serverStream.BeginRead(recvMemory.GetBuffer(),(int)(recvMemory.Position),size,ar => {
                int length = serverStream.EndRead(ar);
                recvMemory.Seek(length,SeekOrigin.Current);
                var eArg = new ReceiveEventArgs{ Length = length, Data = new byte[length]};
                Array.Copy(recvMemory.GetBuffer(),(int)(recvMemory.Position),eArg.Data,0,length);
                if (DataReceived != null) {
                    DataReceived(this,eArg);
                }
            },null);
        }

        internal bool IsConnected() {
            if (clientSocket == null)
                return false;
            return clientSocket.Connected;
        }

        /// <summary>
        /// Benutzen Sie ClearSendBuffer bevor sie eine Sequenz von Daten in den internen Puffer schreiben.<para>
        /// Setzt den Zeiger auf 0 und die Kapazität auf den Defaultwert (<see cref="kByteBufferDefaultSize"/>).</para>
        /// </summary>
        internal void ClearSendBuffer() {
            sendMemory.SetLength(kByteBufferDefaultSize);
            sendMemory.Seek(0,SeekOrigin.Begin);
            //sendBuffer.clear();
        }

        /// <summary>
        /// Benutzen Sie ClearRecvBuffer bevor sie eine Sequenz von Daten in den internen Puffer einlesen.<para>
        /// Setzt den Zeiger auf 0 und die Kapazität auf den Defaultwert (<see cref="kByteBufferDefaultSize"/>).</para>
        /// </summary>
        internal void ClearRecvBuffer() {
            recvMemory.SetLength(kByteBufferDefaultSize);
            //recvMemory.Seek(0,SeekOrigin.Begin);
            recvMemory.Position = 0;
        }

        /// <summary>
        /// Benutzen Sie FlipSendBuffer, wie eine Art truncate, vor dem Senden.<para>
        /// Setzt das Pufferende auf die aktuelle Schreibposition und diesen Zeiger anschließend an den Anfang.</para>
        /// </summary>
        internal void FlipSendBuffer() {
            sendMemory.SetLength(sendMemory.Position);
            sendMemory.Seek(0,SeekOrigin.Begin);
            //sendBuffer.flip();
        }

        /// <summary>
        /// Benutzen Sie FlipRecvBuffer, wie eine Art truncate, vor dem Auslesen empfangener Daten.<para>
        /// Setzt das Pufferende auf die aktuelle Schreibposition und diesen Zeiger anschließend an den Anfang.</para>
        /// </summary>
        internal void FlipRecvBuffer() {
            recvMemory.SetLength(recvMemory.Position);
            recvMemory.Seek(0,SeekOrigin.Begin);
        }

        /// <summary>
        /// Liefert die angegebene Anzahl integers aus dem internen Recieve Buffer.<para>
        /// Der interne Buffer sollte zuvor mit einer Recv-Methode gefüllt werden.</para>
        /// </summary>
        /// <param name="howMany">Anzahl zu lesender ints.</param>
        /// <returns>int-Array der Länge 'howMany'</returns>
        internal int[] GetInts(int howMany) {
            int[] returnArray = new int[howMany];
            for (int i = 0; i < howMany; i++) {
                returnArray[i] = recvMemReader.ReadInt32();
            }
            //for (int i = 0; i < howMany*4; i+=4) {
            //    recvOffset += i;
            //    if (BitConverter.IsLittleEndian) {
            //        Array.Reverse(recvBuffer,recvOffset,4);
            //    }
            //    returnArray[i] = BitConverter.ToInt32(recvBuffer,recvOffset);
            //}
            return returnArray;
        }

        /// <summary>
        /// Liefert die angegebene Anzahl doubles aus dem internen Receive Buffer.<para>
        /// Der interne Buffer sollte zuvor mit einer Recv-Methode gefüllt werden.</para>
        /// </summary>
        /// <param name="howMany">Anzahl zu lesender Double.</param>
        /// <returns>double-Array der Länge 'howMany'</returns>
        internal double[] GetDoubles(int howMany) {
            double[] returnArray = new double[howMany];
            for (int i = 0; i < howMany; i++) {
                returnArray[i] = recvMemReader.ReadDouble();
            }
            //for (int i = 0; i < howMany*8; i+=8) {
            //    recvOffset += i;
            //    if (BitConverter.IsLittleEndian) {
            //        Array.Reverse(recvBuffer,recvOffset,8);
            //    }
            //    returnArray[i] = BitConverter.ToDouble(recvBuffer,recvOffset);
            //}
            return returnArray;
        }

        /// <summary>
        /// Liefert ein Int32 aus dem internen Receive Buffer.<para>
        /// Der interne Buffer sollte zuvor mit einer Recv-Methode gefüllt werden.</para>
        /// </summary>
        /// <returns>int</returns>
        internal int GetInt() {
            return recvMemReader.ReadInt32();
            //int result = BitConverter.ToInt32(recvBuffer,recvOffset);
            //recvOffset += 4;
            //return result;
        }

        /// <summary>
        /// Gets the int the specified network buffer index.<para>
        /// The Read pointer remains unchanged.
        /// </para>
        /// </summary>
        /// <param name="index">The index.</param>
        /// <returns></returns>
        internal int GetInt(int index) {
            byte[] bytes = new byte[4];
            long position = recvMemory.Position;
            recvMemory.Position = index;
            recvMemory.Read(bytes, 0, 4);//liest relativ zur Position des Streams!?
            recvMemory.Position = position;
            if (BitConverter.IsLittleEndian)
                Array.Reverse(bytes);
            return BitConverter.ToInt32(bytes,0);
        }

        /// <summary>
        /// Liefert ein Double aus dem internen Receive Buffer.<para>
        /// Der interne Buffer sollte zuvor mit einer Recv-Methode gefüllt werden.</para>
        /// </summary>
        /// <returns>double</returns>
        internal double GetDouble() {
            return recvMemReader.ReadDouble();
            //double result = BitConverter.ToDouble(recvBuffer,recvOffset);
            //recvOffset += 8;
            //return result;
        }


        /// <summary>
        /// Liest ein (8 Bit) ASCII Zeichen aus dem internen Receive Buffer.
        /// </summary>
        /// <returns>char</returns>
        /// <remarks>
        /// Original Java-Glue Beschreibung:<para>
        /// @since 2.0
        /// Pull a char off the buffer.  Slightly tricky because chars are unicode
        /// (more than a byte) in Java, but only 1 byte in our network protocol.
        /// Thanks: http://bytes.com/forum/thread17160.html
        /// @return ascii character encoded by the next byte.</para>
        /// </remarks>
        internal char GetChar() {
            Encoding encoding = Encoding.ASCII;
            byte[] bytes = new byte[]{recvMemReader.ReadByte()};
            char[] result = new char[1];
            encoding.GetDecoder().GetChars(bytes,0,1,result,0);
            return result[0];
            //byte[] temp = new byte[2];
            //temp[1] = recvBuffer[recvOffset];
            //char result = BitConverter.ToChar(temp,0);
            //recvOffset += 1;
            //return result;
        }

        /// <summary>
        /// Gets a string from the receive buffer.
        /// </summary>
        /// <returns>a String from the network buffer</returns>
        /// <remarks>
        /// Used for getting task spec and env/agent messages. UNLIKE the
        /// charArrays in observations/actions/etc, these strings are null
        /// terminated?
        /// </remarks>
        internal String GetString() {
            string returnString = "";
            int recvLength = this.GetInt();
            //UTF8Encoding encoding = new UTF8Encoding(false, true);
            try {
                byte[] bytes = recvMemReader.ReadBytes(recvLength);
                returnString = Encoding.ASCII.GetString(bytes,0,bytes.Length);
            }
            catch (Exception ex) {
                Trace.Fail("Exception reading String from buffer",ex.Message);
                Console.WriteLine("Exception reading String from buffer");
            }
            return returnString;

        }

        /// <summary>
        /// Gets the observation from the read-buffer.
        /// </summary>
        /// <returns>Read Observation</returns>
        internal generic.Observation<T> GetObservation<T>() where T : struct {
            T[] values = GetGenericValues<T>();
            generic.Observation<T> returnVal = new generic.Observation<T>(values);
            return returnVal;
        }

        /// <summary>
        /// Gets the observation (state) from the read-buffer.
        /// </summary>
        /// <returns>Read Observation (state)</returns>
        internal GeneralState GetObservation() {
            return GetGeneralValues<GeneralState>();
        }

        /// <summary>
        /// Reads an Action out of the receive buffer.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns>Read Action</returns>
        internal generic.Action<T> GetAction<T>() where T : struct {
            T[] values = GetGenericValues<T>();
            var returnVal = new generic.Action<T>(values);
            return returnVal;
        }

        /// <summary>
        /// Reads an Action out of the receive buffer.
        /// </summary>
        /// <returns>Read Action</returns>
        internal GeneralAction GetAction() {
            return GetGeneralValues<GeneralAction>();
        }
        
        /// <summary>
        /// Liest aus dem internen Receive Puffer ein Daten Array.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns>int[], double[], char[] or empty T-Array in case of exception.</returns>
        /// <remarks>
        /// Dies ist in etwa das Äquivalent zu fillAbstractType() der Java-Glue.
        /// </remarks>
        internal T[] GetGenericValues<T>() {
            int numInts = GetInt();
            int numDoubles = GetInt();
            int numChars = GetInt();
            object values = null;
            if (numInts != 0){
                values = GetInts(numInts);
            } 
            else if (numDoubles != 0){
                values = GetDoubles(numDoubles);
            }
            else if (numChars != 0){
                char[] temp = new char[numDoubles];
                for (int i = 0; i < numChars; ++i) {
                    temp[i] = GetChar();
                }
                values = temp;
            }
            else {
                values = new T[0];
            }
            return (T[])values;
        }

        /// <summary>
        /// Gets the general values from the read buffer.
        /// </summary>
        /// <typeparam name="T">Type of <see cref="Bettzueche.RLGlue.RLTypes.IGeneralType">IGeneralType</see></typeparam>
        /// <returns>a general Rl-Type, read out from receive buffer.</returns>
        /// <remarks>Equivalent to fillAbstractType() at Java-Glue.</remarks>
        internal T GetGeneralValues<T>()
            where T : IGeneralType, new() 
        {
            T retVal = new T();
            int numInts    = GetInt();
            int numDoubles = GetInt();
            int numChars   = GetInt();
            retVal.IntValues    = GetInts(numInts);
            retVal.DoubleValues = GetDoubles(numDoubles);
            retVal.CharValues = new char[numChars];
            for (int i = 0; i < numChars; ++i) {
                retVal.CharValues[i] = GetChar();
            }
            return retVal;
        }

        /// <summary>
        /// Puts the ints into the send buffer.
        /// </summary>
        /// <param name="values">The values.</param>
        internal void PutInts(int[] values) {
            if (values == null) {
                return;
            }
            this.ensureSendCapacityRemains(Protocol.kIntSize * values.Length);
            foreach (int item in values) {
                sendMemWriter.Write(item);
            }
        }

        /// <summary>
        /// Puts the doubles into the send buffer.
        /// </summary>
        /// <param name="values">The values.</param>
        internal void PutDoubles(double[] values) {
            if (values == null) {
                return;
            }
            this.ensureSendCapacityRemains(Protocol.kDoubleSize * values.Length);
            foreach (double item in values) {
                sendMemWriter.Write(item);
            }
        }

        /// <summary>
        /// Puts the specified int into the send buffer.
        /// </summary>
        /// <param name="value">The value.</param>
        internal void PutInt(int value) {
            this.ensureSendCapacityRemains(Protocol.kIntSize);
            sendMemWriter.Write(value);
        }

        /// <summary>
        /// Puts the specified double into the send buffer.
        /// </summary>
        /// <param name="value">The value.</param>
        internal void PutDouble(double value) {
            this.ensureSendCapacityRemains(Protocol.kDoubleSize);
            sendMemWriter.Write(value);
        }

        /// <summary>
        /// Puts the specified char into the send buffer.
        /// </summary>
        /// <param name="c">The char.</param>
        /// <remarks>
        /// The network protocol uses one byte char-encoding (ASCII). The specified char will
        /// be internally converted.
        /// </remarks>
        internal void PutChar(char c) {
            this.ensureSendCapacityRemains(Protocol.kCharSize);
            byte[] bytes = Encoding.ASCII.GetBytes(new char[]{c});
            sendMemWriter.Write(bytes);
        }

        /// <summary>
        /// Puts the specified string into the send buffer.
        /// </summary>
        /// <param name="message">The string message.</param>
        internal void PutString(String message) {
            // We don't want to have to deal null...
            if (message == null) {
                message = "";
            }
            this.ensureSendCapacityRemains(Protocol.kIntSize + message.Length);
            this.PutInt(message.Length);

            if (message.Length > 0) {
                sendMemWriter.Write(Encoding.ASCII.GetBytes(message));
                //sendBuffer.put(message.getBytes("UTF-8"));
            }
        }

        /// <summary>
        /// Fügt das angegebene Objekt in den Sendepuffer ein.<para>
        /// Benutzen Sie <see cref="Network.Send"/> zum Absenden.</para>
        /// </summary>
        /// <typeparam name="T">Type des Inhalts (int, double oder char)</typeparam>
        /// <param name="obj"></param>
        internal void PutAbstractNetType<T>(generic.RL_Abstract_Net_Type<T> obj) where T : struct {
            IGeneralType dummy = new GeneralAction(); // could also be a GeneralState, doesn't matter
            //Type[] t = obs.GetType().GetGenericArguments();
            if (typeof(T).Equals(typeof(int))) {
                dummy.IntValues = (int[])(object)obj.Values;
            }
            if (typeof(T).Equals(typeof(double))) {
                dummy.DoubleValues = (double[])(object)obj.Values;
            }
            if (typeof(T).Equals(typeof(char))) {
                dummy.CharValues = (char[])(object)obj.Values;
            }
            else {
                // Pech gehabt, T entspricht nicht der RL-Glue Spezifikation
                Debug.WriteLine("Unsupported RL_Abstract_Type (not int, double or char)","Warning");
            }
            PutGeneralType(dummy);
        }

        /// <summary>
        /// Puts the general type into the send buffer.
        /// </summary>
        /// <param name="obj">The obj.</param>
        internal void PutGeneralType(IGeneralType obj) {
            this.ensureSendCapacityRemains(Network.SizeOf(obj));

            int numInts = obj.IntCount;
            int numDoubles = obj.DoubleCount;
            int numChars = obj.CharCount;

            this.PutInt(numInts);
            this.PutInt(numDoubles);
            this.PutInt(numChars);
            PutInts(obj.IntValues);
            PutDoubles(obj.DoubleValues);

            //Not implementing a putChars because each char is currently
            //converted manually from a byte
            for (int i = 0; i < numChars; ++i) {
                this.PutChar(obj.CharValues[i]);
            }
        }

        /// <summary>
        /// Puts the observation into the send buffer.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj">The obj.</param>
        internal void PutObservation<T>(generic.Observation<T> obs) where T : struct {
            PutAbstractNetType(obs);
        }

        /// <summary>
        /// Puts the specified state into the send buffer.
        /// </summary>
        /// <param name="obj">The obj.</param>
        internal void PutObservation(GeneralState obs) {
            PutGeneralType(obs);
        }

        /// <summary>
        /// Puts the specified action into the send buffer.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj">The obj.</param>
        internal void PutAction<T>(generic.Action<T> action) where T : struct {
            PutAbstractNetType(action);
        }

        /// <summary>
        /// Puts the specified action into the send buffer.
        /// </summary>
        /// <param name="obj">The obj.</param>
        internal void PutAction(GeneralAction action) {
            PutGeneralType(action);
        }

        /// <summary>
        /// Puts the reward observation into send buffer.
        /// </summary>
        /// <typeparam name="O">Type of Observation</typeparam>
        /// <param name="rewardObservation">The reward observation.</param>
        internal void PutRewardObservation<O>(generic.Reward_Observation_Terminal<O> rewardObservation) where O : struct {
            this.ensureSendCapacityRemains(Network.SizeOf<O>(rewardObservation));

            this.PutInt(rewardObservation.Terminal ? 1 : 0);
            this.PutDouble(rewardObservation.Reward);
            this.PutObservation<O>(rewardObservation.Observation);
        }

        /// <summary>
        /// Puts the reward observation into send buffer.
        /// </summary>
        /// <param name="reward">The reward.</param>
        /// <param name="observation">The observation.</param>
        /// <param name="terminal">terminal flag.</param>
        internal void PutRewardObservation(double reward, GeneralState observation, bool terminal) {
            this.ensureSendCapacityRemains(Protocol.kDoubleSize + Network.SizeOf(observation) + Protocol.kIntSize);

            this.PutInt(terminal ? 1 : 0);
            this.PutDouble(reward);
            this.PutObservation(observation);
        }

        /// <summary>
        /// Expands the send buffer if necessary to the required number of bytes.
        /// </summary>
        /// <param name="capacity">Required buffer capacity in bytes.</param>
        protected virtual void ensureSendCapacityRemains(int capacity) {
            if (sendMemory.Length - sendMemory.Position < capacity) {
                sendMemory.SetLength(sendMemory.Length + capacity);
            }
        }

        /// <summary>
        /// Expands the receive buffer if necessary to the required number of bytes.
        /// </summary>
        /// <param name="capacity">Required buffer capacity in bytes.</param>
        protected virtual void ensureRecvCapacityRemains(int capacity) {
            if (recvMemory.Length - sendMemory.Position < capacity) {
                recvMemory.SetLength(sendMemory.Length + capacity);
            }
        }

        //protected static ByteBuffer cloneWithCapacity(ByteBuffer original, int capacity) {
        //    ByteBuffer clone = ByteBuffer.allocateDirect(capacity);
        //    original.flip();
        //    clone.put(original);
        //    clone.position(original.position());
        //    return clone;
        //}

        /// <summary>
        /// Gets size of the specified int in bytes as needed in this network protocol.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns>Size in bytes.</returns>
        internal static int SizeOf(int value) {
            return Protocol.kIntSize;
        }

        /// <summary>
        /// Gets size of the specified double in bytes as needed in this network protocol.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns>Size in bytes.</returns>
        internal static int SizeOf(double value) {
            return Protocol.kDoubleSize;
        }

        /// <summary>
        /// Gets size of the specified string in bytes as needed in this network protocol.
        /// </summary>
        /// <param name="str">The String.</param>
        /// <returns>Size in bytes.</returns>
        internal static int SizeOf(string str) {
            int size = Protocol.kIntSize;
            if (str != null) {
                size += str.Length;
            }
            return size;
        }

        /// <summary>
        /// Gets size of the specified type in bytes as needed in this network protocol.
        /// </summary>
        /// <typeparam name="T">Type of the Abstract_Net_Type</typeparam>
        /// <param name="theObject">The object.</param>
        /// <returns>Size in bytes.</returns>
        internal static int SizeOf<T>(generic.RL_Abstract_Net_Type<T> theObject) where T : struct {
            int size = Protocol.kIntSize * 3; // die 3 NumX-Felder von RL_abstract_type
            if (typeof(T).Equals(typeof(int))) {
                size += Protocol.kIntSize * theObject.Length;
            }
            if (typeof(T).Equals(typeof(double))) {
                size += Protocol.kDoubleSize * theObject.Length;
            }
            if (typeof(T).Equals(typeof(char))) {
                size += Protocol.kCharSize * theObject.Length;
            }
            return size;
        }

        /// <summary>
        /// Gets size of the specified type in bytes as needed in this network protocol.
        /// </summary>
        /// <param name="theObject">The object.</param>
        /// <returns>Size in bytes.</returns>
        internal static int SizeOf(IGeneralType theObject) {
            int size = Protocol.kIntSize * 3;
            int intSize = 0;
            int doubleSize = 0;
            int charSize = 0;

            if (theObject != null) {
                if (theObject.IntValues != null) {
                    intSize = Protocol.kIntSize * theObject.IntCount;
                }
                if (theObject.DoubleValues != null) {
                    doubleSize = Protocol.kDoubleSize * theObject.DoubleCount;
                }
                if (theObject.CharValues != null) {
                    charSize = Protocol.kCharSize * theObject.CharCount;
                }
            }
            return size + intSize + doubleSize + charSize;
        }

        /// <summary>
        /// Gets size of the specified type in bytes as needed in this network protocol.
        /// </summary>
        /// <typeparam name="O">Type of the Observation</typeparam>
        /// <param name="rewardObservation">The reward observation.</param>
        /// <returns>Size in bytes.</returns>
        internal static int SizeOf<O>(generic.Reward_Observation_Terminal<O> rewardObservation) where O : struct {
            int size = Protocol.kIntSize * 3;
            size += Protocol.kIntSize +   // terminal
                    Protocol.kDoubleSize; // reward
            if (typeof(O).Equals(typeof(int))) {
                size += Protocol.kIntSize * rewardObservation.Observation.Length;
            }
            if (typeof(O).Equals(typeof(double))) {
                size += Protocol.kDoubleSize * rewardObservation.Observation.Length;
            }
            if (typeof(O).Equals(typeof(char))) {
                size += Protocol.kCharSize * rewardObservation.Observation.Length;
            }

            return size;
        }
    }



}
